////////////////////////////////////////////////////////////////////////////////
//
//  The MIT License
//
//  Copyright (c) 2008 - 2010. Dimarik
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package ru.goodcore.utils.swf
{

import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.Endian;

import ru.goodcore.utils.StringUtils;
import ru.goodcore.utils.getDefinitionNames;
import ru.goodcore.utils.traceByteArray;

/**
 *  @author				Dimarik
 *  @version			1.1
 *  @langversion		3.0
 *  @playerversion		9.0
 */
public class SWFMovieClip extends ByteArray {

	//-------------------------------------------------------------------------
	//
	//   Constructor
	//
	//-------------------------------------------------------------------------

	/**
	 * Constructor
	 * 
	 * @param input		array of source bitmapdatas
	 * @param qname		full qualified name
	 * 
	 */

	public function SWFMovieClip(input:Array/* of BitmapData */, qname:QName) {
		super();
		super.endian = Endian.LITTLE_ENDIAN;

		var ba:ByteArray = new ByteArray();
		ba.endian = Endian.LITTLE_ENDIAN;

		// DefineSceneAndFrameLabelData
		var data:Array = [
			0x09535746, 0x00000000, 0xA0000A30, 0x00010C00, 
			0x00081144, 0x02430000, 0xBFFFFFFF, 0x00000B15,
			0x53000100, 0x656E6563, 0x00003120
		];

		var len:uint = data.length;

		for (var i:uint=0; i<len; super.writeUnsignedInt(data[i++])) {}
		
		// DoABC
		this.writeBytesFromString('BF 14 7B 01 00 00 01 00 00 00 00 10 00 2E 00 00', ba);
		this.writeBytesFromString('00 00', ba);
		
		if (qname.uri) {
			ba.writeByte(0x0E);
			ba.writeByte(qname.uri.length);
			ba.writeUTFBytes(qname.uri);
		} else {
			ba.writeShort(0x0C00);
		}
		ba.writeByte(qname.localName.length);
		ba.writeUTFBytes(qname.localName);

		this.writeBytesFromString('0D 66 6C 61 73 68 2E 64 69 73 70 6C 61 79 09 4D', ba);
		this.writeBytesFromString('6F 76 69 65 43 6C 69 70', ba);

		len = qname.localName.length + 1;

		if (qname.uri)
			len += qname.uri.length;

		ba.writeByte(len);

		if (qname.uri)
			ba.writeUTFBytes(qname.uri + ':');

		ba.writeUTFBytes(qname.localName);
		
		
		this.writeBytesFromString('   00 06 4F 62 6A 65 63 74 0C 66 6C 61 73 68 2E', ba);
		this.writeBytesFromString('65 76 65 6E 74 73 0F 45 76 65 6E 74 44 69 73 70', ba);
		this.writeBytesFromString('61 74 63 68 65 72 0D 44 69 73 70 6C 61 79 4F 62', ba);
		this.writeBytesFromString('6A 65 63 74 11 49 6E 74 65 72 61 63 74 69 76 65', ba);
		this.writeBytesFromString('4F 62 6A 65 63 74 16 44 69 73 70 6C 61 79 4F 62', ba);
		this.writeBytesFromString('6A 65 63 74 43 6F 6E 74 61 69 6E 65 72 06 53 70', ba);
		this.writeBytesFromString('72 69 74 65 06 16 01 16 03 18 05 16 06 16 08 00', ba);
		this.writeBytesFromString('09 07 01 02 07 02 04 07 04 07 07 05 09 07 02 0A', ba);
		
		this.writeBytesFromString('07 02 0B 07 02 0C 07 02 0D 03 00 00 00 00 00 00', ba);
		this.writeBytesFromString('00 00 00 00 00 00 00 01 01 02 08 03 00 01 00 00', ba);
		this.writeBytesFromString('00 01 02 01 01 04 01 00 03 00 01 01 09 0A 03 D0', ba);
		this.writeBytesFromString('30 47 00 00 01 01 01 0A 0B 06 D0 30 D0 49 00 47', ba);
		this.writeBytesFromString('00 00 02 02 01 01 09 27 D0 30 65 00 60 03 30 60', ba);
		this.writeBytesFromString('04 30 60 05 30 60 06 30 60 07 30 60 08 30 60 02', ba);
		this.writeBytesFromString('30 60 02 58 00 1D 1D 1D 1D 1D 1D 1D 68 01 47 00', ba);
		this.writeBytesFromString('00', ba);


/*
		data = [
			0x00000000, 0x00100000, 0x10000000, 0x00002E00,
			0x00090000, 0x6D754E06
		];

		for (i=0, len=data.length; i<len; ba.writeUnsignedInt(data[i++])) {}

		ba.writeShort(0x6562); ba.writeByte(0x72);

		if (qname.uri) {
			ba.writeByte(qname.uri.length);
			ba.writeUTFBytes(qname.uri);
		}

		ba.writeByte(qname.localName.length);
		ba.writeUTFBytes(qname.localName);

		data = [
			0x616C660D, 0x642E6873, 0x6C707369, 0x420A7961,
			0x616D7469, 0x74614470
		];

		for (i=0, len=data.length; i<len; ba.writeUnsignedInt(data[i++])) {}

		ba.writeByte(0x61);

		len = qname.localName.length + 1;

		if (qname.uri)
			len += qname.uri.length;

		ba.writeByte(len);

		if (qname.uri)
			ba.writeUTFBytes(qname.uri + ':');

		ba.writeUTFBytes(qname.localName);

		data = [
			0x6A624F06, 0x05746365, 0x03160116, 0x07180516,
			0x01070500, 0x04020702, 0x07060307, 0x00030801,
			0x02000000, 0x00010100, 0x00000000, 0x02010000,
			0x00040803, 0x00000001, 0x02010201, 0x03000104,
			0x04010100, 0x30D00305, 0x01000047, 0x06050303,
			0xD030D008, 0x0249D2D1, 0x02000047, 0x04010102,
			0x6530D013, 0x30046000, 0x60300360, 0x1D005803,
			0x4702681D
		];

		for (i=0, len=data.length; i<len; ba.writeUnsignedInt(data[i++])) {}

		ba.writeShort(0x0000);
*/
		this.writeTag(0x52, ba);
		super.writeBytes(ba);
		//trace(traceByteArray(this));

		var imgCount:	uint = input.length;
		var charID:		uint = 1;
		var bmpd:		BitmapData;

		for (i = 0; i < imgCount; i++, charID += 2) {
			ba.length = 0;
			bmpd = BitmapData(input[i]);
			this.writeDefineBitsLossless2( bmpd, charID, ba);
			super.writeBytes(ba);

			ba.length = 0;
			//var tempRect:ByteArray = new ByteArray();
			//this.writeBytesFromString('50 00 B4 00 0A 00', tempRect);

			this.writeDefineShape( bmpd.rect, charID + 1, charID, ba);
			//this.writeDefineShape( new Rectangle(0, 0, 32, 32), charID + 1, charID, ba);
			super.writeBytes(ba);
		}


		// Write all Bitmaps and Shapes
		//trace(traceByteArray(this))

		// DefineSprite
		ba.length = 0;
		// RECORDHEADER (long)
		ba.writeByte(0x00); // 1
		ba.writeByte(0x00); // 2
		ba.writeByte(0x00); // 3
		ba.writeByte(0x00); // 4
		ba.writeByte(0x00); // 5
		ba.writeByte(0x00); // 6

		// Sprite ID UI16
		ba.writeShort(imgCount * 2 + 1); //

		// FrameCount UI16
		ba.writeShort(imgCount); //

		// Control Tags

		// PlaceObject2
		this.writeBytesFromString('86 06 06 01 00 02 00 00', ba);

		// ShowFrame
		ba.writeShort(0x0040); // Tag type = 01

		charID = 4;
		while (charID <= imgCount * 2) {
			// PlaceObject2 && ShowFrame
			this.writeBytesFromString('85 06 03 01 00', ba);
			ba.writeShort(charID);
			// ShowFrame
			ba.writeShort(0x0040); // Tag type = 01
			charID += 2;
			
		}

		ba.writeShort(0x0000); // End Tag

		this.writeTag(0x27, ba); // Tag type = 39

		super.writeBytes(ba);

		// SymbolClass
		ba.length = 0;
		ba.writeUnsignedInt(0x00000000); ba.writeUnsignedInt(0x00010000);
		ba.writeShort(imgCount * 2 + 1);

		if (qname.uri)
			ba.writeUTFBytes(qname.uri + '.');

		ba.writeUTFBytes(qname.localName); ba.writeByte(0x00);

		this.writeTag(0x4C, ba); // Tag type = 76

		super.writeBytes(ba);
		super.writeUnsignedInt(0x000040);

		super.position = 4;
		super.writeUnsignedInt(super.length);
		super.position = 0;

		//trace(traceByteArray(this));

		//trace(getDefinitionNames(this, true, false));
	}

	//-------------------------------------------------------------------------
	//
	//   Private methods
	//
	//-------------------------------------------------------------------------

	/**
	 * 
	 * @param ba
	 * @param bmpd
	 * 
	 */
	private function writeDefineBitsLossless2(bmpd:BitmapData, charID:uint, ba:ByteArray):void {
		// RECORDHEADER (long)
		ba.writeByte(0x00); // 1
		ba.writeByte(0x00); // 2
		ba.writeByte(0x00); // 3
		ba.writeByte(0x00); // 4
		ba.writeByte(0x00); // 5
		ba.writeByte(0x00); // 6

		// CharacterID UI16
		ba.writeShort(charID);

		// BitmapFormat UI8
		ba.writeByte(0x05); // 0x05 32-bit ARGB image

		// BitmapWidth UI16
		ba.writeShort(bmpd.width);

		// BitmapHeight UI16
		ba.writeShort(bmpd.height);

		var data:ByteArray = bmpd.getPixels(bmpd.rect);
		data.compress();

		// ZlibBitmapData
		ba.writeBytes( data );

		// Tag type = 36
		this.writeTag(0x24, ba);
	}

	/**
	 * 
	 * @param ba
	 * @param bmpd
	 * 
	 */
	private function writeDefineShape(rect:Rectangle, charID:uint, BitmapId:uint, ba:ByteArray):void {
		// RECORDHEADER (long)
		ba.writeByte(0x00); // 1
		ba.writeByte(0x00); // 2
		ba.writeByte(0x00); // 3
		ba.writeByte(0x00); // 4
		ba.writeByte(0x00); // 5
		ba.writeByte(0x00); // 6

		// ShapeId UI16
		ba.writeShort(charID);

		// ShapeBounds RECT
		this.writeRect(rect, ba);

		// SHAPEWITHSTYLE
		
		//  - FILLSTYLEARRAY
		//		- FillStyleCount UI8
		ba.writeByte(0x01);
		
		//		- FillStyles
		//			- FillStyleType UI8
		ba.writeByte(0x41); // Fill with 0x43 = non-smoothed clipped bitmap
		//			- BitmapId UI16
		ba.writeShort(BitmapId);
		//			- BitmapMatrix MATRIX
		this.writeBytesFromString('D9 40 00 05 00 00 00', ba);
		
		//  - LINESTYLEARRAY
		//		- LineStyleCount UI8
		ba.writeByte(0x00);
		
		// NumFillBits & NumLineBits
		ba.writeByte(0x10);

		var width:			uint = rect.height * 20;
		var height:			uint = rect.width * 20;

		var maxValue:		uint = Math.max(width, height);

		var numBitsCount:	uint = maxValue.toString(2).length - 1;
		var numBits:		String = StringUtils.setNumberLen(numBitsCount, 4, 2);
		var bitsValue:		String;
		var record:			String;

		// ShapeRecords
		//		- STYLECHANGERECORD
		var buffer:String = '0001001';

		//		- STRAIGHTEDGERECORD
		bitsValue = '0' + StringUtils.setNumberLen(width , numBitsCount + 1, 2);
		record = '11' + numBits + '00' + bitsValue;
		buffer += record;

		//		- STRAIGHTEDGERECORD
		bitsValue = '0' + StringUtils.setNumberLen(height, numBitsCount + 1, 2);
		record = '11' + numBits + '01' + bitsValue;
		buffer += record;

		//		- STRAIGHTEDGERECORD
		var bitString:String = width.toString(2);
		var aaa:String = bitString.replace(/0/g, '2');
		aaa = aaa.replace(/1/g, '0');
		aaa = aaa.replace(/2/g, '1');
		var ddd:uint = parseInt(aaa, 2) + 1;		
		bitsValue = StringUtils.setNumberLen(ddd, numBitsCount + 1, 2);
		bitsValue = '1' + bitsValue;
		record = '11' + numBits + '00' + bitsValue;
		buffer += record;

		//		- STRAIGHTEDGERECORD
		bitString = height.toString(2);
		aaa = bitString.replace(/0/g, '2');
		aaa = aaa.replace(/1/g, '0');
		aaa = aaa.replace(/2/g, '1');
		ddd = parseInt(aaa, 2) + 1;		
		bitsValue = StringUtils.setNumberLen(ddd, numBitsCount + 1, 2);
		bitsValue = '1' + bitsValue;
		record = '11' + numBits + '01' + bitsValue;
		buffer += record;

		var byte:uint;
		while (buffer.length > 8) {
			byte = parseInt( buffer.substr(0, 8), 2);
			ba.writeByte(byte);
			buffer = buffer.substr(8);
		}

		while (buffer.length < 8) {
			buffer += '0';
		}
		byte = parseInt( buffer.substr(0, 8), 2);
		ba.writeByte(byte);
		ba.writeByte(0x00);

		// Tag type = 2
		this.writeTag(0x02, ba);
	}



	private function writeRect(rect:Rectangle, ba:ByteArray):void {
		var xMin:Number = 0;
		var xMax:Number = rect.width * 20;
		var yMin:Number = 0;
		var yMax:Number = rect.height * 20;

		var max:Number = Math.max(xMax, yMax);
		var numBits:uint = max.toString(2).length + 1;

		var target:String = StringUtils.setNumberLen(numBits, 5, 2);
		target += StringUtils.setNumberLen(xMin, numBits, 2);
		target += StringUtils.setNumberLen(xMax, numBits, 2);
		target += StringUtils.setNumberLen(yMin, numBits, 2);
		target += StringUtils.setNumberLen(yMax, numBits, 2);

		var pad:uint = 0;

		if (target.length % 8) {
			pad = 8 - target.length % 8;
		}
		while (pad--) target += '0';

		var byte:uint;
		while (target.length) {
			byte = parseInt(target.substr(0, 8), 2);
			target = target.substr(8);
			ba.writeByte(byte);
		}
	}

	/**
	 * @private
	 * @param id	tag id
	 * @param ba	bytearray
	 */	
	private function writeTag(id:uint, ba:ByteArray):void {
		ba.position = 0;
		ba.writeShort(id << 6 | 0x3F);
		ba.writeUnsignedInt(ba.length - 6);
	}

	/**
	 * @private
	 * @param bytesHexString
	 * @param ba
	 */
	private function writeBytesFromString(bytesHexString:String, ba:ByteArray=null):void {
		bytesHexString = bytesHexString.replace(/\s/g, '');

		var length:uint = bytesHexString.length;

		if (!ba)
			ba = this;

		for (var i:uint = 0; i < length; i += 2) {
			var hexByte:String = bytesHexString.substr(i, 2);
			var byte:uint = parseInt(hexByte, 16);
			ba.writeByte(byte);
		}
	}

}
}

